using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using SKBKontur.Serialization.Binding;
using Binder=SKBKontur.Serialization.Binding.Binder;

namespace SKBKontur.Serialization
{
    public class StringToByteArraySerializer : IStringToByteArraySerializer
    {
        #region IStringToByteArraySerializer Members

        public void DeserializeTo<T>(ref T objectToAssignProperties, Dictionary<string, byte[]> serializedData)
        {
            PropertyInfo[] properties = typeof (T).GetProperties();
            var firstBinders = new Dictionary<string, Binder>();
            var lastBinders = new Dictionary<string, Binder>();
            foreach (PropertyInfo property in properties)
            {
                foreach (object attribute in property.GetCustomAttributes(true))
                    if (attribute is Binder)
                    {
                        var binder = (Binder) attribute;
                        if (binder.IsLast())
                            lastBinders[property.Name] = binder;
                        else
                            firstBinders[property.Name] = binder;
                        break;
                    }
            }
            Bind(firstBinders, ref objectToAssignProperties, serializedData);
            Bind(lastBinders, ref objectToAssignProperties, serializedData);
        }

        public Dictionary<string, byte[]> Serialize<T>(T objectToSaveProperties)
        {
            var serializedData = new Dictionary<string, byte[]>();
            PropertyInfo[] properties = typeof (T).GetProperties();
            var firstBinders = new Dictionary<string, Binder>();
            var lastBinders = new Dictionary<string, Binder>();
            foreach (PropertyInfo property in properties)
            {
                foreach (object attribute in property.GetCustomAttributes(true))
                    if (attribute is Binder)
                    {
                        var binder = (Binder) attribute;
                        if (binder.IsLast())
                            lastBinders[property.Name] = binder;
                        else
                            firstBinders[property.Name] = binder;
                        break;
                    }
            }
            Unbind(firstBinders, objectToSaveProperties, serializedData);
            Unbind(lastBinders, objectToSaveProperties, serializedData);
            return serializedData;
        }

        public string GetColumnName(PropertyInfo propertyInfo)
        {
            foreach (object attribute in propertyInfo.GetCustomAttributes(true))
                if (attribute is ScalarBinder)
                {
                    var binder = (ScalarBinder)attribute;
                    return binder.GetPropertyKeyName(propertyInfo);
                }
            throw new SerializationException(string.Format("Property {0} was not marker with scalar binder", propertyInfo));
        }

        public byte[] GetPropertyValue(PropertyInfo propertyInfo, object value)
        {
            foreach (object attribute in propertyInfo.GetCustomAttributes(true))
                if (attribute is ScalarBinder)
                {
                    var binder = (ScalarBinder)attribute;
                    return binder.SerializeValue(value);
                }
            throw new SerializationException(string.Format("Property {0} was not marker with scalar binder", propertyInfo));
        }

        #endregion

        private static void Unbind(Dictionary<string, Binder> binders, object objectToSaveProperties,
                                   Dictionary<string, byte[]> serializedData)
        {
            foreach (var binder in binders)
                binder.Value.Unbind(objectToSaveProperties, binder.Key, ref serializedData);
        }

        private static void Bind<T>(Dictionary<string, Binder> binders, ref T objectToAssignProperties,
                                    Dictionary<string, byte[]> serializedData)
        {
            object objectToBind = objectToAssignProperties;
            foreach (var binder in binders)
                binder.Value.Bind(ref objectToBind, binder.Key, serializedData);
        }
    }
}